package tech

import (
	"fmt"
	"slices"
	"ssp/set"
)

func (g *Grid) AvoidRectangle2Filled(lines []*House,
	aRHandler func([]*House, int, []int, int) (bool, []*Result)) (used bool, results []*Result) {
	for i, line1 := range lines {
		pairs := combine(chooseFilled(line1), 2)
		for _, pair := range pairs {
			for j := range lines {
				if i == j {
					continue
				}

				if !withinTwoBoxes([]int{i, j}, pair) {
					continue
				}
				if ok, rs := aRHandler(lines, i, pair, j); ok {
					if OUTPUTRESULT {
						results = append(results, rs...)
					}
					used = true
					if !EXHAUSTIVE {
						return
					}
				}
			}
		}
	}
	return
}

func (g *Grid) AvoidRectangle1Filled(lines []*House,
	aRHandler func([]*House, int, []int, int) (bool, []*Result)) (used bool, results []*Result) {
	for i, line1 := range lines {
		pairs := chooseFilledAndUnsolved(line1)
		for _, pair := range pairs {
			for j := range lines {
				if i == j {
					continue
				}

				if !withinTwoBoxes([]int{i, j}, pair) {
					continue
				}
				if ok, rs := aRHandler(lines, i, pair, j); ok {
					if OUTPUTRESULT {
						results = append(results, rs...)
					}
					used = true
				}
				if !EXHAUSTIVE {
					return
				}
			}
		}
	}
	return
}
func (s *Solve) AvoidRectangleType1(g *Grid) (used bool, results []*Result) {
	return g.AvoidRectangle2Filled(g.lines[:9], g.ARType1)
}
func (g *Grid) ARType1(lines []*House, i int, pair []int, j int) (used bool, results []*Result) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if !ps[2].filled() && !ps[3].filled() {
		return
	}

	var rmCand int
	var rmCell *Cell
	var refCells []*Cell
	if ps[2].value == ps[1].value && ps[3].cands.Has(toCand(ps[0].value)) {
		rmCand = toCand(ps[0].value)
		rmCell = ps[3]
		refCells = ps[:3]
	} else if ps[3].value == ps[0].value && ps[2].cands.Has(toCand(ps[1].value)) {
		rmCand = toCand(ps[1].value)
		rmCell = ps[2]
		refCells = []*Cell{ps[0], ps[1], ps[3]}
	} else {
		return
	}
	used = true
	if OUTPUTRESULT {
		result := &Result{}
		result.addRemoved(set.NewSet(rmCand), []*Cell{rmCell})
		result.addHelpValues(refCells)
		result.setStrategyName("AR Type1")
		results = append(results, result)
	}
	if CHANGE {
		removeCands(set.NewSet(rmCand), []*Cell{rmCell})
	}
	return
}

func chooseFilled(line *House) (filleds []int) {
	for cellNum, cell := range line.cells {
		if cell.filled() {
			filleds = append(filleds, cellNum)
		}
	}
	return
}

func chooseFilledAndUnsolved(line *House) (pairs [][]int) {
	filled := []int{}
	unsolved := []int{}
	for i, cell := range line.cells {
		if cell.filled() {
			filled = append(filled, i)
			continue
		}
		if !cell.solved() {
			unsolved = append(unsolved, i)
		}
	}
	for _, i := range filled {
		for _, j := range unsolved {
			pairs = append(pairs, []int{i, j})
		}
	}
	return
}

func (s *Solve) AvoidRectangleType2A(g *Grid) (used bool, results []*Result) {
	if ok, rs := g.AvoidRectangle2Filled(g.lines, g.ARType2A); ok {
		if OUTPUTRESULT {
			results = append(results, rs...)
		}
		used = true
	}
	return
}

func (g *Grid) ARType2A(lines []*House, i int, pair []int, j int) (used bool, results []*Result) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if !ps[3].cands.Has(toCand(ps[0].value)) || !ps[2].cands.Has(toCand(ps[1].value)) {
		return
	}

	if ps[2].cands.Count() != 2 || ps[3].cands.Count() != 2 {
		return
	}

	z := ps[2].cands.Intersect(ps[3].cands)
	if z.Count() != 1 {
		return
	}
	//cand := z.GetList()[0]

	if ok, rmCells := g.canRemoveSeenCands(z, ps[2:]); ok {
		used = true
		if OUTPUTRESULT {
			result := new(Result)
			result.addRemoved(z, rmCells)
			result.addHelpValues(ps[:2])
			g1 := result.getHelpCandsGroup()
			g1.add(ps[2].cands.Diff(z), []*Cell{ps[2]})
			g1.add(ps[3].cands.Diff(z), []*Cell{ps[3]})
			g2 := result.getHelpCandsGroup()
			g2.add(z, ps[2:])
			result.setStrategyName("AR Type2A")
			results = append(results, result)
		}
		if CHANGE {
			removeCands(z, rmCells)
		}
	}
	return
}

func (s *Solve) AvoidRectangleType2B(g *Grid) (used bool, results []*Result) {
	return g.AvoidRectangle1Filled(g.lines[:9], g.aRType2B)
}

func (g *Grid) aRType2B(lines []*House, i int, pair []int, j int) (used bool, results []*Result) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if ps[1].cands.Count() != 2 || ps[1].cands != ps[2].cands {
		return
	}

	var cands set.Set

	if ps[3].value == ps[0].value && ps[3].filled() {
		cands = ps[1].cands
	} else if ps[3].cands.Has(toCand(ps[0].value)) &&
		ps[3].cands.Count() == 2 && ps[3].cands.Intersect(ps[1].cands).Count() == 1 {
		cands = ps[1].cands.Diff(ps[3].cands)
	} else {
		return
	}
	if ok, rmCell := g.canRemoveSeenCands(cands, ps[1:3]); ok {
		used = true
		if OUTPUTRESULT {
			result := new(Result)
			result.addRemoved(cands, rmCell)
			g1 := result.getHelpCandsGroup()
			g1.add(cands, ps[1:3])
			if ps[3].value != 0 {
				result.addHelpValues([]*Cell{ps[0], ps[3]})
			} else {
				result.addHelpValues([]*Cell{ps[0]})
				g2 := result.getHelpCandsGroup()
				g2.add(ps[3].cands, ps[3:])
				g2.add(ps[1].cands.Diff(cands), ps[1:3])
			}
			result.setStrategyName("AR Type2B")
			results = append(results, result)
		}
		if CHANGE {
			removeCands(cands, rmCell)
		}
	}

	return
}

func (s *Solve) AvoidRectangleType3A(g *Grid) (used bool, results []*Result) {
	if ok, rs := g.AvoidRectangle2Filled(g.lines, g.ARType3A); ok {
		if OUTPUTRESULT {
			results = append(results, rs...)
		}
		used = true
	}
	/*
		if ok, rs := g.AvoidRectangle2Filled(g.cols[:], g.ARType3A); ok {
			if OUTPUTRESULT {
				results = append(results, rs...)
			}
			used = true
		}*/
	return
}

func (g *Grid) ARType3A(lines []*House, i int, pair []int,
	j int) (used bool, results []*Result) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if !ps[3].cands.Has(toCand(ps[0].value)) || !ps[2].cands.Has(toCand(ps[1].value)) {
		return
	}
	if ps[2].cands.Union(ps[3].cands).Count() == 1 {
		return
	}

	cells := []*Cell{}
	for _, cell := range lines[j].cells {
		if cell.solved() || (slices.Contains(ps[2:], cell)) {
			continue
		}
		cells = append(cells, cell)
	}
	houses := [][]*Cell{cells}
	if ps[2].boxNum == ps[3].boxNum {
		cells := []*Cell{}
		for _, cell := range g.boxes[ps[2].boxNum].cells {
			if cell.solved() || (slices.Contains(ps[2:], cell)) {
				continue
			}
			cells = append(cells, cell)
		}
		houses = append(houses, cells)
	}

	for _, cells := range houses {
		if ok, subset, subsetCands, rmCells := g.verifyNaked(
			unionCellCands(ps[2:]).Diff(set.NewSet(toCand(ps[0].value), toCand(ps[1].value))), ps[2:], cells); ok {
			used = true
			if OUTPUTRESULT {
				result := new(Result)
				if len(result.Removed) != 0 {
					rtemp := Result{}
					rtemp.addRemoved(subsetCands, rmCells)
					if slices.Equal(rtemp.Removed, result.Removed) {
						break
					}

				}
				result.addHelpValues(ps[:2])
				result.addRemoved(subsetCands, rmCells)
				g1 := result.getHelpCandsGroup()
				g1.add(set.NewSet(toCand(ps[0].value)), ps[3:])
				g1.add(set.NewSet(toCand(ps[1].value)), ps[2:3])
				g2 := result.getHelpCandsGroup()
				g2.add(subsetCands, append(subset, ps[2:]...))
				result.setStrategyName("AR Type3A")
				results = append(results, result)
			}
			if CHANGE {
				removeCands(subsetCands, rmCells)
			}
			if !EXHAUSTIVE {
				return
			}
		}
	}

	return
}

func (s *Solve) AvoidRectangleType4C(g *Grid) (used bool, results []*Result) {
	return g.AvoidRectangle1Filled(g.lines[:9], g.aRType4C)
}

func (g *Grid) aRType4C(lines []*House, i int, pair []int, j int) (used bool, results []*Result) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if !ps[3].cands.Has(toCand(ps[0].value)) {
		return
	}

	cands := ps[1].cands.Intersect(ps[2].cands).Intersect(ps[3].cands)

	if cands.Count() == 0 {
		return
	}
	//fmt.Println(ps)

	removeCand := -1
	row := g.rows[ps[3].rowNum]
	col := g.cols[ps[3].colNum]
	colNums := pair
	rowNums := []int{i, j}
	if lines[i].kind == 1 {
		colNums = []int{i, j}
		rowNums = pair
	}
	//fmt.Println(i, j, pair)
	//fmt.Println(colNums, rowNums)
	var strongCand int
	var strongCell0, strongCell1, strongCell2 *Cell
	//, strongCell3 *Cell
	for _, cand := range cands.GetList() {
		if onlyCellsHasCand(cand, row, []*Cell{row.cells[colNums[0]], row.cells[colNums[1]]}) &&
			onlyCellsHasCand(cand, col, []*Cell{col.cells[rowNums[0]], col.cells[rowNums[1]]}) {
			removeCand = toCand(ps[0].value)
			strongCand = cand
			strongCell0 = row.cells[colNums[0]]
			strongCell1 = row.cells[colNums[1]]
			strongCell2 = col.cells[rowNums[0]]
			//strongCell3 = col.cells[rowNums[1]]
			break
		}
	}
	if removeCand == -1 {
		return
	}
	used = true
	//ps[3].removeCand(removeCand)
	if OUTPUTRESULT {
		result := new(Result)
		result.addRemoved(set.NewSet(removeCand), []*Cell{ps[3]})
		result.addHelpValues(ps[0:1])
		g1 := result.getHelpCandsGroup()
		g1.add(set.NewSet(removeCand), ps[1:])
		result.addHelpChains(strongLink(strongCand, strongCell1, strongCell0))
		result.addHelpChains(strongLink(strongCand, strongCell1, strongCell2))
		//result.addHelpLinks(strongLink(strongCand, strongCell1, strongCell3))
		result.setStrategyName("AR Type4C")
		results = append(results, result)
	}
	if CHANGE {
		ps[3].removeCand(removeCand)
	}
	return
}

func strongLink(cand int, c0, c1 *Cell) string {
	return fmt.Sprintf("R%vC%v[%v]=R%vC%v[%v]",
		c0.rowNum+1, c0.colNum+1, cand+1, c1.rowNum+1, c1.colNum+1, cand+1)
}

func (s *Solve) AvoidRectangleType5(g *Grid) (used bool, results []*Result) {
	return g.AvoidRectangle1Filled(g.lines[:9], g.aRType5)
}

func (g *Grid) aRType5(lines []*House, i int, pair []int, j int) (used bool, results []*Result) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if !ps[3].filled() || ps[3].value != ps[0].value {
		return
	}

	//if ps[1].cands.Count() != 2 || ps[2].cands.Count() != 2 {
	//	return
	//}

	if ps[1].cands.Intersect(ps[2].cands).Count() == 0 {
		return
	}

	cands := ps[1].cands

	if ok, rmCell := g.canRemoveSeenCands(cands, ps[1:3]); ok {
		used = true
		if OUTPUTRESULT {
			result := new(Result)
			result.addRemoved(cands, rmCell)
			g1 := result.getHelpCandsGroup()
			g1.add(cands, ps[1:3])
			result.addHelpValues([]*Cell{ps[0], ps[3]})
			result.setStrategyName("AR Type5")
			results = append(results, result)
		}
		if CHANGE {
			removeCands(cands, rmCell)
		}
	}

	return
}

/*
45[679] [89][789][12] [67][12](3)
[37][26][169] [39][12]5 [67](4)(8)
[378][28][17] 64[37] (5)[12]9

978 [345][25][24] [23]6(1)
(2)[46][46] 1[89][39] [38]7(5)
(5)(1)3 [78][2678][26] [28](9)(4)

[78]3(2) [457][156][146] 9[58][67]
6[48][457] [579]3[79] 1[58]2
1(9)[57] 2[567]8 4(3)[67]

*/

func (s *Solve) AvoidRectangleType4D(g *Grid) (used bool, results []*Result) {
	OUTPUTRESULT = true
	ok, rs := g.CounterDR(g.cdr4)
	if !ok {
		return
	}
	for _, r := range rs {
		if len(r.HelpChains) != 1 {
			continue
		}
		if len(r.HelpValues) == 0 {
			continue
		}
		results = append(results, r)
		used = true
	}
	OUTPUTRESULT = false
	return
}
